# -*- coding: utf-8 -*-
from com.maycur.grpc.base.base_entity import java_utils_dict, JavaMethodBuilder
from com.maycur.grpc.base.base_tools import append_import_to_line
from conf.entity.module.grpc_config import ProtoConfig, GrpcConfig
from com.maycur.grpc.base.java_loader import JavaClassInfo, JavaLoader
from com.maycur.utils.JavaAstUtils import make_document_comment
import os
import re


class ExceptionHandlerGenerator:
    config: GrpcConfig
    proto_config: ProtoConfig
    service_exception: JavaClassInfo
    service_exception_changed: bool

    def __init__(self, config: GrpcConfig, is_exist=False):
        self.config = config
        self.proto_config = config.proto_config
        self.service_exception_changed = False

        # init service_exception
        service_exception = JavaClassInfo(is_exist=is_exist)
        service_exception.full_name = f'com.maycur.{config.proto_config.package}.exception.ServiceException'
        service_exception.simple_name = 'ServiceException'
        service_exception.package = f'com.maycur.{config.proto_config.package}.exception'
        service_exception.path = f'{config.proto_config.project_path}/src/main/java/com/maycur/' \
                                 f'{config.proto_config.package}/exception'
        # set reference
        self.service_exception = service_exception

        # check service_exception
        service_exception_path = service_exception.path
        service_exception_file = f'{service_exception_path}/{service_exception.simple_name}.java'
        if not os.path.exists(service_exception_path) or not os.path.exists(service_exception_file):
            service_exception.lines.append(self.gen_service_exception_full_code())
        else:
            # check getDescription
            loader = JavaLoader()
            loader.load_by_class(service_exception.path, f'{service_exception.simple_name}.java')
            service_exception = loader.simple_name_dict[service_exception.simple_name]
            if 'getDescription' not in service_exception.method_dict:
                # the line under package
                import_append_idx = 1
                append_import_to_line(service_exception, java_utils_dict['MessageFormat'], import_append_idx)

                new_m = JavaMethodBuilder(4)

                new_m.add_line('public String getDescription() {', 1)
                new_m.add_line('return MessageFormat.format(errorCode.getErrorMsg(), extraMsg);', 2)
                new_m.add_line('}', 1)

                origin_class_close_line = service_exception.lines[service_exception.class_close_idx]
                service_exception.lines[service_exception.class_close_idx] = new_m.build() + origin_class_close_line

                self.service_exception_changed = True
        # set reference again
        self.service_exception = service_exception

    def get_full_package(self):
        return f'{self.proto_config.servant.root_package}.{self.proto_config.servant.exception_handler_path}'

    def gen_package_line(self):
        return f'package {self.get_full_package()};'

    def gen_service_exception_package_line(self):
        return f'package {self.service_exception.package};'

    def gen_handler_import_lines(self):
        return f"""

import {self.service_exception.full_name};
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import org.slf4j.Logger;"""

    @staticmethod
    def get_handler_codes():
        return make_document_comment('Maycur Tool') + """
@FunctionalInterface
public interface ExceptionHandler {

    /**
     * abstract handler to be implemented
     */
    void handler();

    /**
     * handle gRPC invocation exception
     *
     * @param identity the identity, such as entCode
     * @param streamObserver the streamObserver
     * @param logger logger
     */
    default void handleException(String identity, StreamObserver streamObserver, Logger logger){
        try {
            this.handler();
        } catch (ServiceException e) {
            streamObserver.onError(Status.INTERNAL.withDescription(e.getDescription()).withCause(e).asRuntimeException());
        } catch (Exception e) {
            logger.error("unknown exception, ExceptionHandler identity is {}", identity, e);
            streamObserver
                .onError(Status.UNKNOWN.withDescription(e.getMessage()).withCause(e.getCause()).asRuntimeException());
        }
    }
}
        """

    def gen_service_exception_import_lines(self):
        return f"""

import com.maycur.{self.proto_config.package}.constant.ErrorCode;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.text.MessageFormat;
import java.util.List;
import java.util.stream.Collectors;"""

    @staticmethod
    def get_service_exception_codes():
        return make_document_comment('Maycur Tool') + """
public class ServiceException extends RuntimeException {

    private final ErrorCode errorCode;
    private final String extraMsg;
    private final List<String> args;

    public ServiceException(ErrorCode errorCode) {
        super(errorCode.getErrorMsg());
        this.errorCode = errorCode;
        this.extraMsg = "";
        this.args = null;
    }

    public ServiceException(ErrorCode errorCode, String extraMsg) {
        super(errorCode.getErrorMsg());
        this.extraMsg = extraMsg;
        this.errorCode = errorCode;
        this.args = null;
    }

    public ServiceException(ErrorCode errorCode, Throwable rootCause) {
        super(errorCode.getErrorMsg(), rootCause);
        this.errorCode = errorCode;
        this.extraMsg = "";
        this.args = null;
    }

    public ServiceException(ErrorCode errorCode, List<String> args) {
        super(errorCode.getErrorMsg());
        this.args = args;
        this.errorCode = errorCode;
        this.extraMsg = "";
    }

    public int getErrorCode() {
        return errorCode.getCode();
    }

    public String getErrorMsg() {
        return errorCode.getErrorMsg();
    }

    public String getExtraMsg() {
        return this.extraMsg;
    }

    public String getFullMsg() {
        if (StringUtils.isBlank(this.getExtraMsg())) {
            return this.getMessage();
        }

        return this.getMessage() + " (" + this.getExtraMsg() + ")";
    }

    public List<String> getArgs() {
        return args;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this).append("errorCode", errorCode).append("extraMsg", extraMsg).toString();
    }

    public String getDescription() {
        return MessageFormat.format(errorCode.getErrorMsg(), extraMsg);
    }
}"""

    def gen_handler_full_code(self):
        code_str = self.gen_package_line()
        code_str += self.gen_handler_import_lines()
        code_str += '\n\n'
        return code_str + self.get_handler_codes()

    def gen_service_exception_full_code(self):
        code_str = self.gen_service_exception_package_line()
        code_str += self.gen_service_exception_import_lines()
        code_str += '\n\n'
        return code_str + self.get_service_exception_codes()

    def generate(self) -> JavaClassInfo:
        handler = JavaClassInfo(is_exist=False)
        handler.simple_name = self.proto_config.servant.exception_handler_name
        handler.full_name = self.get_full_package() + '.' + handler.simple_name
        handler_path = f'{self.proto_config.project_path}/{self.proto_config.servant.root_path}'
        handler_path += f'/{self.proto_config.servant.exception_handler_path}'
        handler_path += f'/{self.proto_config.servant.exception_handler_name}.java'
        handler.path = re.sub('/+', '/', handler_path)
        handler.lines.append(self.gen_handler_full_code())
        return handler
